// jackspa, a dirt-simple LADSPA host for JACK
// Copyright (C) 2007 Nick Thomas
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the GNU General Public
// License as published by the Free Software Foundation; the terms of
// any later version are NOT APPLICABLE.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

#include <dlfcn.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jackspa.hpp"

// Finds the plugin descriptor with the given ID in the given
// object file, storing it in state->descriptor. Returns 1 on
// success. On failure, returns 0 and sets *error to an appropriate
// error message.
int find_plugin(state_t *state, char *file, int id, char **error)
{
    int err = 0;
    int i;
    void *library;
    LADSPA_Descriptor *(*descriptor_fun)(unsigned long index);
    LADSPA_Descriptor *descriptor;

    // Open the library.
    library = dlopen(file, RTLD_LAZY);
    if (!library) {
        *error = dlerror();
        err = 1;
    }

    // Find the ladspa_descriptor() function.
    if (!err) {
        descriptor_fun = (LADSPA_Descriptor *(*)(unsigned long))
            dlsym(library, "ladspa_descriptor");
        if (!descriptor_fun) {
            *error = dlerror();
            err = 1;
        }
    }

    // Find the appropriate descriptor.
    for (i = 0; !err; i++) {
        descriptor = descriptor_fun(i);

        if (!descriptor) {
            *error = "no such plugin index in the given file";
            err = 1;
        } else if ((int)descriptor->UniqueID == id) {
            state->descriptor = descriptor;
            break;
        }
    }

    return !err;
}

// The JACK processing callback.
int process(jack_nframes_t nframes, void *arg)
{
    state_t *state = (state_t *)arg;
    int i, j;
    float *buffer = state->control_port_buffer;

    // Connect audio ports and copy control port values.
    for (i = 0; i < (int)state->descriptor->PortCount; i++) {
        if (LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[i]) &&
            LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[i])) {
            for (j = 0; j < (int)nframes; j++, buffer++) {
                *buffer = state->control_port_values[i];
            }
        } else {
            state->descriptor->connect_port
                (state->handle, i,
                 (float *)jack_port_get_buffer(state->ports[i], nframes));
        }
    }

    // Run the plugin.
    state->descriptor->run(state->handle, nframes);

    return 0;
}

// The JACK buffer size callback.
int buffer_size(jack_nframes_t nframes, void *arg)
{
    state_t *state = (state_t *)arg;
    float *buffer;
    int i;

    state->control_port_buffer = (float *)realloc
        (state->control_port_buffer, state->num_control_ports *
         nframes * sizeof(float));

    buffer = state->control_port_buffer;

    for (i = 0; i < (int)state->descriptor->PortCount; i++) {
        if (LADSPA_IS_PORT_CONTROL(state->descriptor->
                                   PortDescriptors[i]) &&
            LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[i])) {
            state->descriptor->connect_port(state->handle, i, buffer);
            buffer += jack_get_buffer_size(state->jack_client);
        }
    }

    return 0;
}

// Initializes JACK, opening the appropriate ports, instantiating the
// LADSPA Plugin, and starting the synthesis thread running.. Returns
// 1 on success. On failure, returns 0 and sets *error to an
// appropriate error message.
int init_jack(state_t *state, char **error)
{
    static char client_name_prefix[] = "jackspa_";
    int err = 0;
    jack_status_t jack_status;
    int i;
    unsigned long flags;
    float *buffer;

    // Allocate memory for the client name.
    state->client_name = (char *)calloc(1, sizeof(client_name_prefix) +
                                        strlen(state->descriptor->Label));
    if (!state->client_name) {
        err = 1;
        *error = strerror(errno);
    }

    // Set the client name.
    if (!err) {
        strcat(state->client_name, client_name_prefix);
        strcat(state->client_name, state->descriptor->Label);
    }

    // Open JACK.
    if (!err) {
        state->jack_client = jack_client_open(state->client_name,
                                              JackNullOption, &jack_status);
        if (jack_status) {
            *error = "could not connect to JACK";
            err = 1;
        }
    }

    // Allocate memory for the list of ports.
    if (!err) {
        state->ports = (jack_port_t **)calloc(state->descriptor->PortCount,
                                              sizeof(jack_port_t *));
        if (!state->ports) {
            *error = strerror(errno);
            err = 1;
        }
    }

    // Allocate memory for the list of control port values.
    if (!err) {
        state->control_port_values =
            (float *)malloc(state->descriptor->PortCount * sizeof(float));
        if (!state->control_port_values) {
            *error = strerror(errno);
            err = 1;
        }
    }

    // Register ports.
    state->num_control_ports = 0;
    for (i = 0; !err && i < (int)state->descriptor->PortCount; i++) {
        if (LADSPA_IS_PORT_CONTROL(state->descriptor->
                                   PortDescriptors[i]) &&
            LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[i])) {
            state->num_control_ports++;
            continue;
        }

        if (LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[i])) {
            flags = JackPortIsInput;
        } else {
            flags = JackPortIsOutput;
        }

        state->ports[i] =
            jack_port_register(state->jack_client,
                               state->descriptor->PortNames[i],
                               JACK_DEFAULT_AUDIO_TYPE,
                               flags, 0);

        if (!state->ports[i]) {
            err = 1;
            *error = "could not register JACK ports";
        }
    }

    // Register our processing callback.
    if (!err) {
        if (jack_set_process_callback(state->jack_client, &process, state)) {
            err = 1;
            *error = "could not register the JACK processing callback";
        }
    }

    // Register our buffer size callback.
    if (!err) {
        if (jack_set_buffer_size_callback(state->jack_client, &buffer_size,
                                          state)) {
            err = 1;
            *error = "could not register the JACK processing callback";
        }
    }

    // Instantiate the LADSPA plugin.
    if (!err) {
        state->handle = state->descriptor->instantiate
            (state->descriptor, jack_get_sample_rate(state->jack_client));
        if (!state->handle) {
            err = 1;
            *error = "could not instantiate the plugin.";
        }
    }

    // Allocate control port buffers.
    if (!err) {
        state->control_port_buffer = (float *)malloc
            (state->num_control_ports *
             jack_get_buffer_size(state->jack_client) *
             sizeof(float));
        if (!state->control_port_buffer) {
            err = 1;
            *error = strerror(errno);
        }
    }

    // Connect control ports.
    if (!err) {
        buffer = state->control_port_buffer;

        for (i = 0; i < (int)state->descriptor->PortCount; i++) {
            if (LADSPA_IS_PORT_CONTROL(state->descriptor->
                                       PortDescriptors[i]) &&
                LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[i])) {
                state->descriptor->connect_port(state->handle, i, buffer);
                buffer += jack_get_buffer_size(state->jack_client);
                state->control_port_values[i] = 0.0;
            }
        }
    }

    // Activate the LADSPA plugin.
    if (!err) {
        if (state->descriptor->activate) {
            state->descriptor->activate(state->handle);
        }
    }

    // Get the bits flowing.
    if (!err) {
        if (jack_activate(state->jack_client)) {
            err = 1;
            *error = "could not activate audio processing";
        }
    }

    return !err;
}

typedef enum {
    st_want_plugin_file,
    st_want_plugin_id,
    st_done
} arg_parser_state;

// Parses command-line arguments. Returns 1 on success. On failure,
// returns 0 and sets *error to an appropriate error message.
int parse_args(state_t *state, int argc, char **argv, char **plugin_file,
               int *plugin_id, char **error)
{
    int err = 0;
    int i;
    arg_parser_state st = st_want_plugin_file;

    argc--;
    argv++;

    for (i = 0; i < argc; i++) {
        switch (st) {
         case st_want_plugin_file:
            *plugin_file = argv[i];
            st = st_want_plugin_id;
            break;

         case st_want_plugin_id:
            *plugin_id = atoi(argv[i]);
            st = st_done;
            break;

         case st_done:
            break;

         default:
            break;
        }
    }

    if (st != st_done) {
        err = 1;
        *error = "must supply a plugin file and a plugin ID";
    }

    return !err;
}

void jackspa_init(state_t *state, int argc, char **argv)
{
    char *error;
    int err = 0;
    char *plugin_file;
    int plugin_id;

    state->jack_client = 0;

    err = !parse_args(state, argc, argv, &plugin_file, &plugin_id, &error);

    if (!err) {
        err = !find_plugin(state, plugin_file, plugin_id, &error);
    }

    if (!err) {
        err = !init_jack(state, &error);
    }

    if (err) {
        fprintf(stderr, "Error: %s.\n", error);
    }
}
